'use strict';
(function(host, ns){

if(!(ns in host)){
	host[ns] = {};
}

var ID = 0;
var _tostr = Object.prototype.toString;
var _slice = Array.prototype.slice;


function getId(){ return ++ID; }
function isUndefined(v){ return typeof(v) == 'undefined'; }
function isNull(v){ return !isUndefined(v) && v === null; }
function isNil(v){ return isUndefined(v) || v === null; }
function isObject(o){ return !isUndefined(o) && tostr(o) == '[object Object]'; }
function isArray(o){ return !isUndefined(o) && tostr(o) == '[object Array]'; }
function isDate(o){ return !isUndefined(o) && tostr(o) == '[object Date]' }
function isFunction(v){ return typeof(v) == 'function'; }
function isString(v){ return typeof(v) == 'string'; }
function isNumber(v){ return typeof(v) == 'number'; }
function isValidNumber(v){ return isNumber(v) && !isNaN(v); }
function isBoolean(v){ return typeof(v) == 'boolean'; }
function isInBrowser(){ return typeof(window) != 'undefined'; }
function isInNode(){ return typeof(module) != 'undefined'; }

function toInt(v){
	return v >> 0;
}

function each(o, fn){
	for(var p in o){
		if(fn(o[p], p, o)) return;
	}
}
function EACH(a, fn){
	var i = 0;
	while(i < a.length){
		if(fn(a[i], i, a)) return;
		i++;
	}
}
function HCAE(a, fn){
	var i = a.length - 1;
	while(i >= 0){
		if(fn(a[i], i, a)) return;
		i--;
	}
}
function slice(a, i){
	i = i || 0;
	return _slice.call(a, i);
}
function tostr(v){
	return _tostr.call(v);
}
function Class(name, base, contruct, protos){
	var fn;
	eval(
	'fn=function ' + name + '(){'
		+ (isFunction(base) ? 'base.apply(this,arguments);' : '')
		+ (isFunction(contruct) ? 'contruct.apply(this,arguments);' : '')
	+ '}'
	);
	isFunction(base) && each(base.prototype, function(v, p){
		fn.prototype[p] = v;
	});
	isObject(protos) && each(protos, function(v, p){
		fn.prototype[p] = v;
	});
	return fn;
}
function map(obj, keys){
	var o = {};
	keys.forEach(function(key){
		o[key] = obj[key];
	});
	return o;
}

function convert(val, fn, arg){
	return isFunction(fn) ? fn(val, arg) : val;
}

function map_callback(obj, keys, methods){
	var o = {};
	keys.forEach(function(key){
		o[key] = convert(obj[key], methods[key], key);
	})
	return o;
}

function compair(keys, a, b){
	var k = [];
	keys.forEach(function(key){
		if(a[key] === b[key]) return;
		k.push(key);
	});
	return k;
}

function regist(host, name, target){
	if(isInBrowser()){
		host = window;
		if(!host[ns]) host[ns] = {}
		host[ns][name] = target;
	} else {
		host[name] = target;
	}
}
function using(name){
	if(isInBrowser()){
		return window[ns][name];
	} else if(isInNode()){
		return require('./' + ns + '.' + name)[name];
	}
}

var $cache$ = [];
function cache(type, target){
	var id = getId();
	$cache$.push({ id: id, type: type, target: target });
	return id;
}
function getCacheById(id){
	var tar = $cache$.find(function(a){
		return a.id === id;
	});
	return tar;
}
function getCacheByType(type){
	var tar = $cache$.filter(function(a){
		return a.type === type;
	});
	return tar;
}
function formatRequestParams(p){

	if(!isObject(p)) return formatRequestParams({});

	if(!p.data || !isObject(p.data)){
		p.data = null;
	}
	if(!p.data && !p.method){
		p.method = 'GET';
	} else if(p.data && !p.method){
		p.method = 'POST';
	}
	p.returnType = p.returnType || 'text';
	p.method = (p.method + '').toUpperCase();
	if(p.method === 'POST'){
		if(p.data && isInBrowser()){
			var formData = new FormData();
			each(p.data, function(v, p){
				formData.append(p, v);
			})
			p.data = formData;
		}
		//multipart/form-data
		p.contentType = p.contentType || 'application/x-www-form-urlencoded';
	} else if(p.method == 'GET') {
		p.contentType = null;
	}
	return p;
}

/* http request method */

/* for node */
function requestNode(p, onload, onerror){
	p = formatRequestParams(p);
	var header = {
		"Content-type": "text/plain; charset=UTF-8",
		"User-Agent": "LIBRA-REQUEST(NODE) by MK",
		"Accept": "text/plain; charset=UTF-8",
		'auth': {
			'user': '',
			'pass': '',
			'sendImmediately': false
		}
	};
	var option = {
		url: p.url,
		json: false,
		headers: header
	};
	var m = p.method == 'GET' ? 'get' : 'post';
	try{
		var req = require('request');
		var f = req[m](p.url, option, function (error, response, body) {

			if(error){
				if(isFunction(onerror)){
					onerror.call(error);
				}
			} else {
				if(response.statusCode >= 400){
					if(isFunction(onerror)){
						onerror({
							status: response.statusCode,
							text: null,
							error: 'Failed ' + response.statusCode
						})
					}
				} else {
					if(isFunction(onload)){
						onload({
							status: response.statusCode,
							text: response.body,
							error: null
						})
					}
				}
			}
			// console.log(response.statusCode, response.body)
			// console.log(error)
		});
		p.data && f.form(p.data);
	} catch(e) {
		if(isFunction(onerror)){
			onerror({
				status: -1,
				error: e,
				text: null
			});
		}
	}
}

 /* for browser */
function requestAjax(p, onload, onerror){
	p = formatRequestParams(p);
	var xhr = new XMLHttpRequest();
	xhr.open(p.method, p.url, true);
	p.type && xhr.setRequestHeader('Content-type', p.type);
	
	xhr.onerror = onerror;
	xhr.onload = function(e){
		if(this.status >= 400){
			if(isFunction(onerror)){
				onerror({
					status: this.status,
					text: null,
					error: 'Failed ' + this.status
				})
			}
		} else {
			if(isFunction(onload)){
				onload({
					status: this.status,
					text: this.responseText,
					error: null
				})
			}
		}
	};
	try{
		xhr.send(p.data);
	} catch(e) {
		if(isFunction(onerror)){
			onerror({
				status: -1,
				error: e,
				text: null
			});
		}
	}
}
/* env match */
var requestMethod = isInBrowser() ? requestAjax : requestNode;

function createPromise(AsyncMethod, args){
	var promise = new Promise(function(resolve, reject){
		AsyncMethod(args || null, resolve, reject);
	});
	return promise;
}

function typeConvert(val, type){
	switch(type){
		case 'int':
			if(isValidNumber(val)) return Math.floor(val);
			return val ? parseInt(val + '') : 0;
		case 'float':
			if(isValidNumber(val)) return val;
			return val ? parseFloat(val + '') : 0;
		case 'bool':
			if(isBoolean(val)) return val;
			return isString(val) ? val.toLowerCase() == 'true' : !!val;
		case 'time':
		case 'date':
			if(isDate(val)) return val;
			if(typeof(val) == 'number'){
				val = Math.floor(val);
				var sub = 13 - Math.ceil(Math.log10(val));
				if(sub > 0){
					val *= Math.pow(10, sub);
				}
				return new Date(val);
			} else if(typeof(val) == 'string'){
				if(/^[\d\.]+$/.test(val)){
					var f = parseFloat(val);
					if(isNaN(f)){
						f = 0;
					} else {
						f = Math.floor(f);
					}
					return typeConvert(f, type);
				}
			}
			return new Date(val + '');
		default:
			return val;
	}
}
function formatInt(n, fixed, forceCut){
	fixed = fixed >> 0;
	n = (n >> 0) + '';
	if(fixed > 0){
		if(n.length < fixed){
			n = Array(fixed - n.length + 1).join('0') + n;
		} else if(n.length > fixed && forceCut){
			n = n.substr(n.length - fixed, fixed);
		}
	}
	return n;
}
function formatNumber(n, fixed, fixedAhead){
	fixed = fixed >> 0;
	if(fixed < 1) return formatInt(Math.round(n), fixedAhead >> 0);;
	var i = formatInt(n >> 0, fixedAhead >> 0);
	var f = Math.round((n - i) * Math.pow(10, fixed))
	return i + '.' + formatInt(f, fixed);
}
function formatDate(date, fmtString){
	fmtString = fmtString && typeof(fmtString) == 'string' ? fmtString : formatDate.DEFAULT_FORMAT;
	var tail = '';
	var str = fmtString.replace(/[ymdhs]+/gi, function(m){
		var ch = m.charAt(0), l = m.length;
		switch(ch){
			case 'y':
				return formatInt(date.getFullYear(), Math.min(4, l), l == 2);
			case 'M':
				return formatInt(date.getMonth() + 1, Math.min(2, l));
			case 'd':
				return formatInt(date.getDate(), Math.min(2, l));
			case 'H':
				return formatInt(date.getHours(), Math.min(2, l));
			case 'h':
				var h = date.getHours();
				if(h > 12){
					h -= 12;
					tail = ' PM'
				} else {
					tail = ' AM'
				}
				return formatInt(h, Math.min(2, l));
			case 'm':
				return formatInt(date.getMinutes(), Math.min(2, l));
			case 's':
				return formatInt(date.getSeconds(), Math.min(2, l));
		}
	});
	str += tail;
	return str;
}
formatDate.DEFAULT_FORMAT = 'yyyy-MM-dd HH:mm:ss';

function DataSchema(preset){
	this.types = {};
	preset && isObject(preset) && this.set(preset);
}

var templateMark = /\{([^\{\}\r\n]+)\}/g;
function format(s, obj){
	if(arguments.length == 2){
		if(isArray(obj)){
			var arr = [];
			obj.forEach(function(data){
				arr.push(format(s, data));
			})
			return arr.join('');
		} else if(obj && isObject(obj)){
			var str = (s + '').replace(templateMark, function(m, $1){
				var idx = $1.indexOf(':');
				var key, fmt;
				if(idx > -1){
					key = $1.substr(0, idx);
					fmt = $1.substr(idx + 1);
				} else {
					key = $1;
					fmt = '';
				}
				if(key in obj){
					return formatValue(obj[key], fmt);
				} else {
					return '';
				}
			});
			return str;
		}
		return s + '';
	} else if(arguments.length > 2) {
		var arr = slice(arguments, 1);
		var str = (s + '').replace(templateMark, function(m, $1){
			var idx = $1.indexOf(':');
			var key, fmt;
			if(idx > -1){
				key = $1.substr(0, idx);
				fmt = $1.substr(idx + 1);
			} else {
				key = $1;
				fmt = '';
			}
			key = parseInt(key);
			if(isNaN(key) || !arr[key]) return '';
			return formatValue(arr[key], fmt);
		});
		return str;
	} else {
		return s + '';
	}
}
function formatValue(val, fmt){
	if(util.isValidNumber(val)){
		if(!fmt) return val + '';
		fmt = fmt.split('.');
		var a = fmt[0] ? fmt[0].length : 0;
		var b = fmt[1] ? fmt[1].length : 0;
		return util.formatNumber(val, b, a)
	} else if(util.isDate(val)){
		return util.formatDate(val, fmt);
	} else {
		return val + '';
	}
}

DataSchema.prototype = {
	set: function(key, type){
		if(key && isObject(key)){
			var _ = this;
			each(key, function(v, k){
				_.set(k, v);
			});
			return this;
		}
		this.types[key] = type;
		return this;
	},
	has: function(key){
		return key in this.types;
	},
	get: function(val, key){
		if(this.has(key)){
			return typeConvert(val, this.types[key]);
		}
		return val;
	},
	getType: function(key){
		return this.has(key) ? this.types[key] : null;
	},
	convert: function(obj){
		if(!obj) return null;
		var data = {}, _ = this;
		if(isFunction(obj.each)){
			obj.each(function(v, p){
				data[p] = _.get(v, p);
			});
		} else if(obj && isObject(obj)) {
			each(obj, function(v, p){
				data[p] = _.get(v, p);
			});
		}
		
		return data;
	}
};

var util = {
	cache: cache,
	getCacheById: getCacheById,
	getCacheByType: getCacheByType,
	getId: getId,
	isUndefined: isUndefined,
	isNull: isNull,
	isNil: isNil,
	isObject: isObject,
	isArray: isArray,
	isDate: isDate,
	isFunction: isFunction,
	isString: isString,
	isNumber: isNumber,
	isValidNumber: isValidNumber,
	isBoolean: isBoolean,
	each: each,
	EACH: EACH,
	HCAE: HCAE,
	toInt: toInt,
	slice: slice,
	tostr: tostr,
	Class: Class,
	regist: regist,
	using: using,
	map: map,
	map_callback: map_callback,
	convert: convert,
	compair: compair,
	formatRequestParams: formatRequestParams,
	request: requestMethod,
	createPromise: createPromise,
	typeConvert: typeConvert,
	formatInt: formatInt,
	formatNumber: formatNumber,
	formatDate: formatDate,
	DataSchema: DataSchema,
	format: format
};

if(isInBrowser()){
	if(!window[ns]){
		window[ns] = {};
	}
	window[ns].util = window['$$$util$$$'] = util;
}
if(isInNode()){
	module.exports = util;
}


})(this, 'libra');